<?php

if( !defined( 'MEDIAWIKI' ) ) {
   echo( "This file is an extension to the MediaWiki software and cannot be used standalone.\n" );
   die( 1 );
}

$wgExtensionCredits['soundsharesearch'][] = array(
   'version'     => '0.0.1',
   'name'        => 'SoundshareSearch',
   'author'      => 'MP2P Technologies',
   'email'       => 'leo at mp2p dot net',
   'url'         => '',
   'description' => 'Replace MediaWiki search engine with custom use of [http://www.sphinxsearch.com/ Sphinx].'
);

# Replace internal search
$wgDisableInternalSearch = true;
$wgDisableSearchUpdate = true;
$wgSearchType = 'SoundshareSearch';

$wgExtensionFunctions[] = "wfSpecialPageSoundshareSearch";
$wgHooks['BeforePageDisplay'][] = "wfSoundshareBeforePageDisplay";


function wfSpecialPageSoundshareSearch()
{
       SpecialPage::addPage( new SoundshareSearch() );
}

function wfSoundshareBeforePageDisplay($o, $html)
{
       global $wgTitle;

       if ($wgTitle->mNamespace==NS_ARTIST || $wgTitle->mNamespace==NS_SONG || $wgTitle->mNamespace==NS_RELEASE)
       {
               	$ss = new SoundshareSearch();
		$ss->showModules();
       }
       return true;
}

global $IP;
require_once($IP.'/includes/SpecialPage.php');
require_once($IP.'/includes/SearchEngine.php');
require_once($IP.'/includes/SoundShareUtils.php');

class SoundshareSearch extends SpecialPage
{
   function SoundshareSearch() {
      global $wgDisableInternalSearch;

       if ($wgDisableInternalSearch) {
           SpecialPage::SpecialPage("Search");
       } else {
           SpecialPage::SpecialPage("SoundshareSearch");
       }
       self::loadMessages();
       return true;
   }

   function loadMessages() {
       static $messagesLoaded = false;

       global $wgMessageCache;
       if ($messagesLoaded) return;
       $messagesLoaded = true;

       $allMessages = array(
           'en' => array(
               'soundsharePreviousPage'       => 'Previous',
               'soundshareNextPage'           => 'Next',
               'soundshareSearchPreamble' => "<div class='searchpreamble'>Results <b>%d</b> - <b>%d</b> of about <b>%d</b> files for %s <b>%s</b></div>",
               'sphinxSearchButton'       => 'Search',
               'soundshareSearchEpilogue' => "<div class='searchepilogue'>%0.3f sec.</div>",
               'sphinxSearchDidYouMean'   => 'Did you mean',
               'sphinxLoading'            => 'Loading...'
           )
       );

       foreach ( $allMessages as $lang => $langMessages ) {
           $wgMessageCache->addMessages( $langMessages, $lang );
       }
       return true;
   }

	function showModules()
	{
       		global $wgOut, $wgTitle, $wgRequest, $wgLang;
		$wgOut->switchBocataMode(false);
		$this->page = 1; //$wgRequest->getInt('page', 1);
		switch ($wgTitle->mNamespace)
		{
			case NS_ARTIST:
				$wgOut->addHTML("<div class='home_module' id='popsongs'><h2>".wfMsg('popular_songs')."</h2>");
				$this->show_popular_songs(10, $wgTitle->mArtist, 0.5);
				$wgOut->addHTML("</div><div class='home_module' id='albums'><h2>".wfMsg('albums')."</h2>");
				$this->show_albums($wgTitle->mArtist, "", "", 0.5);
				$wgOut->addHTML("</div>");
				break;
			case NS_SONG:
				$wgOut->addHTML("<div class='home_module' id='popsongs'><h2>".wfMsg('other_versions')."</h2>");
				$this->show_other_versions($wgTitle->mArtist, $wgTitle->mNoArtist, "", 0.5);
				$wgOut->addHTML("</div><div class='home_module' id='albums'><h2>".wfMsg('related_songs')."</h2>");
				$this->show_related_songs($wgTitle->mArtist, $wgTitle->mNoArtist, 0.5);
				$wgOut->addHTML("</div>");
				break;
			case NS_RELEASE:
				$wgOut->addHTML("<div class='home_module' id='popsongs'><h2>".wfMsg('album_songs')."</h2>");
				$this->show_album($wgTitle->mArtist, $wgTitle->mNoArtist, 0.5);
				$wgOut->addHTML("</div><div class='home_module' id='albums'><h2>".wfMsg('other_albums')."</h2>");
				$this->show_albums($wgTitle->mArtist, "", $wgTitle->mNoArtist, 0.5);
				$wgOut->addHTML("</div>");
				break;
		}
	}	

       function execute() {
               global $wgOut, $wgRequest, $wgTitle;
               $search = mb_ereg_replace('_', ' ', sphinx_escape($wgRequest->getText('search', '')));
               $title = wfMsg('searching')." '".htmlspecialchars($search)."'";
               $wgOut->setDefaultTitle(wfMsg('search_results'));
               $wgTitle->mDisplay = $title;
               $page_size = 20;
               $this->page = $wgRequest->getInt('page', 1);

               $sql_search = mysql_real_escape_string($search);
               $sql = "SELECT coalesce(t.Artist, f.Artist) Artist, coalesce(t.Album, f.Album) Album, coalesce(t.Title, f.Title) Title, coalesce(fn.filename, f.Filename) as Filename, coalesce(t.Length, f.Length) as Length, t.TrackKey Track, s.int2*s.int1 as Pop FROM searches s inner join ss_file f on s.idfile = f.idfile left join ss_tags t on s.idtags = t.idtags inner join ss_filenames fn on s.idfilename = fn.idfilename WHERE s.query='{$sql_search};mode=extended2;offset=".(($this->page-1)*$page_size).";limit=$page_size;maxmatches=100000;ranker=entity;select=idtags as idtags, type as type, TotalCount as TotalCount, idiv(@weight,16777216) as w1, sum(TotalCount) as int2, sum(CountFiles) as int1, idfile, idfilename;fieldweights=Filename,1073741825,Title,10,Album,2,Artist,5;sort=extended:type ASC, w1 DESC, TotalCount DESC, @weight DESC;groupby=attr:songKey;groupsort=type ASC, w1 DESC, int2 DESC, @weight DESC';";
	       $sql_count = "SELECT VARIABLE_VALUE FROM INFORMATION_SCHEMA.SESSION_STATUS WHERE VARIABLE_NAME = 'SPHINX_TOTAL_FOUND' and (select count(id) from searches where query='$sql_search;mode=extended2;maxmatches=100000;limit=1') = 1;";
		$columns = array(Title, Artist, Album, Length, Pop, Filename);
		
		$wgTitle->mDisplay = $title;
                
               	$this->perform_search($columns, $sql, $sql_count, $page_size, 0, 'tsearch', 1);    
	}

	function show_album($artist, $album, $table_width)
	{
		$where = "artistkey='".normalize($artist)."' and albumkey is not null and albumkey='".normalize($album)."'";
		$sql = "select common_string(10, 150, Artist, (log(totalCount)+0.01)*(countTagsOk+0.01)) Artist, common_string(10, 100, Title, (log(totalCount)+0.01)*(countTagsOk+0.01)) Title, common_int(5, Length) Length, best_int(TrackKey, (log(totalCount)+0.01)*(countTagsOk+0.01), 1) Track, sum(countTagsOk+1)*sum(totalCount+1) as Pop from ss_tags where $where group by titlekey order by Pop desc";
		$columns = array(Track, Title, Artist, Length, Pop, More); 
		$this->perform_search($columns, $sql, "", 40, 0.4, 'ttracks', $table_width, Track);
	}

	function show_popular_songs($max_count, $artist, $table_width)
	{
		$where = "artistkey='".normalize($artist)."'";
		$sql = "SELECT common_string(10, 150, Artist, (log(TotalCount)+0.01)*(countTagsOk+0.01)) Artist, common_string(100, 100, Album, if(albumkey is null, 0, (log(TotalCount)+0.01)*(countTagsOk+0.01))) Album, common_string(10, 100, Title, (log(TotalCount)+0.01)*(countTagsOk+0.01)) Title, common_int(5,Length) Length, common_int(5,TrackKey) Track, sum(log(TotalCount)+0.01)*sum(countTagsOk+0.01) as Pop FROM ss_tags WHERE $where group by artistkey, titlekey order by Pop DESC limit $max_count";
		$columns = array(Title, Album, Length, Pop);
		$this->perform_search($columns, $sql, "", $max_count, 0, 'tpopsongs', $table_width);
	}

	function show_albums($artist, $song="", $album="", $table_width)
	{
		$page_size = 25;
		$where = "artistkey='".normalize($artist)."' and albumkey is not null";
		if ($song!="") $where .= " and titlekey='".normalize($song)."'";
		else if ($album!="") $where .= " and albumkey!='".normalize($album)."'";
		
		$sql = "select * from (select common_string(10, 150, Artist, log(TotalCount)*CountFiles*Weight) Artist, common_string(100, 100, Album, log(TotalCount)*CountFiles*Weight) Album, common_int(5, year) Year, log(sum(TotalCount+0.01))*sum(countTagsOk+0.01) as Pop from (select artistKey, albumkey, album, artist, year, totalcount, countfiles, weight, weight*totalcount as songWeight, (case when trackkey is null then 0.00001 when trackkey=1 then 0.25 else 1 end) as trackWeight, countTagsOk from ss_tags where $where order by songWeight*trackWeight desc) t group by albumkey order by Pop desc limit $page_size) t order by Pop desc";
		$sql_count = "";
		$columns = array(Year, Album, Pop, More);
		$this->perform_search($columns, $sql, $sql_count, $page_size, 0, 'talbums', $table_width, Year);
	}

	function show_other_versions($artist, $song, $table_width)
	{
		$page_size = 10;
		//$where = "artistkey='".normalize($artist)."' or
		$where = "titlekey='".normalize($song)."'";
		$sql = "SELECT artistkey, common_string(10, 150, Artist, log(TotalCount)*CountFiles*Weight) Artist, common_string(100, 100, Album, if(albumkey is null, 0, 1)*log(TotalCount)*CountFiles*CountTagsOk) Album, common_int(5,Length) Length, common_int(5,TrackKey) Track, sum(TotalCount+0.01)*sum(countTagsOk+0.01) as Pop FROM ss_tags WHERE $where group by artistkey, titlekey having artistkey!='".normalize($artist)."' order by Pop DESC limit $page_size";
		$sql_count = "";
		$columns = array(TitleArtist, Album, Length, Pop);
		$this->perform_search($columns, $sql, "", $page_size, 0, 'tsongversions', $table_width);
	}

	function show_related_songs($artist, $song, $table_width)
	{
		$page_size = 10;
		$where = "artistkey='".normalize($artist)."' and titlekey!='".normalize($song)."'";
		$sql = "SELECT common_string(10, 150, Artist, log(TotalCount)*CountFiles*Weight) Artist, common_string(100, 100, Album, if(albumkey is null, 0, 1)*log(TotalCount)*CountFiles*counttagsok) Album, common_string(10, 100, Title, log(TotalCount)*CountFiles*Weight) Title, common_int(5,Length) Length, common_int(5,TrackKey) Track, sum(TotalCount+0.01)*sum(countTagsOk+0.01) as Pop FROM ss_tags WHERE $where group by artistkey, titlekey order by Pop DESC limit 10;";
		$sql_count = "";
		$columns = array(Title, Album, Length, Pop);
		$this->perform_search($columns, $sql, "", 1, $page_size, 0, 'trelsongs', $table_width);
	}

       function perform_search(&$columns, $query, $count_query, $page_size, $weight_threshold, $table_id, $table_width, $group_column = null)
       {
               	global $wgOut, $wgRequest, $wgArtistPop, $wgTitle;
		static $colWidths = null;
		if ($colWidths==null) $colWidths = array(Year => 40, Track => 20, Title => 0.35, Artist => 0.25, Album => 0.3, Length => 40, Pop => 80, More => 20, Filename=>-1);
	       	$start_time = microtime(true);
         	$dbr = wfGetDB( DB_SLAVE );
               	$res = $dbr->query($query);
		$count = 0;
		$total_pop = 0;

		// if has results to show...
		if ($row=$dbr->fetchRow($res))
		{
			foreach ($columns as $col) 
			{
				if ($colWidths[$col]<0) continue;
				$cw = $colWidths[$col]; 
				if ($cw<1) $nw += $cw; else $nw += $cw/800*$table_width; 
			}
			$wgOut->addHTML("<table cellspacing=0 cellpadding=0 class='searchresults' id='$table_id'><tr class='filesheader'>");

			foreach ($columns as $col) 
			{
				if ($colWidths[$col]<0) continue;
				$cw = $colWidths[$col];
				$wgOut->addHTML("<td width='$cw' class='$col'>".wfMsg("searchresults_$col")."</td>");
			}
			$wgOut->addHTML("</tr>");

			$count = $group_count = 0;
                	do {
				$song = "";
				foreach ($columns as $col) {	
					$multirow = false;
					$style="";

					switch ($col)
					{
						case Year:
							$style = " class='year'";
							$value = $row[$col];
							if ($value<1901 || $value>date("Y")+1) $value = null;
							if ($value==null) {
								$value="?";
								if ($group_column==Year) $gname = 0;
							} else if ($group_column==Year) 
								$gname = $value;
							break;
						case Track:
							$style = " class='track'";
							$value = $row[$col];
							if ($value==null) {
								$value="?";
								if ($group_column==Track) $gname = 0;
							} else if ($group_column==Track) 
								$gname = 100-$value;
							break;
						case Artist:
							$html = htmlspecialchars($row[$col], ENT_QUOTES);
							$title = SoundshareTitle::newFromText($row[$col], NS_ARTIST);
							$new = $title->isKnown()?"":" new";
							$style = " class='artist ellipsis $new' title='$html'";
							$value = "<a href='".htmlspecialchars($title->getLocalUrl(), ENT_QUOTES)."'>$html</a>";
							break;	
						case Title:
							$html = htmlspecialchars($row[$col], ENT_QUOTES);
							$title = SoundshareTitle::newFromText(str_replace("/", "", $row[Artist])."/".$row[$col], NS_SONG);
							$new = $title->isKnown()?"":" new";
							$style = " class='artist ellipsis $new' title='$html'";
							$value = "<a href='".htmlspecialchars($title->getLocalUrl(), ENT_QUOTES)."'>$html</a>";
							break;					
						case TitleArtist:
							$html = htmlspecialchars($row[Artist], ENT_QUOTES);
							$title = SoundshareTitle::newFromText(str_replace("/", "", $row[Artist])."/".$wgTitle->mNoArtist, NS_SONG);
							$new = $title->isKnown()?"":" new";
							$style = " class='title ellipsis $new' title='$html'";
							$value = "<a href='".htmlspecialchars($title->getLocalUrl(), ENT_QUOTES)."'>$html</a>";
							break;		
						case Album:
							$html = htmlspecialchars($row[$col], ENT_QUOTES);
							$title = SoundshareTitle::newFromText(str_replace("/", "", $row[Artist])."/".$row[$col], NS_RELEASE);
							$new = $title->isKnown()?"":" new";
							$style = " class='artist ellipsis $new' title='$html'";
							$value = "<a href='".htmlspecialchars($title->getLocalUrl(), ENT_QUOTES)."'>$html</a>";
							break;	
						case Length:
							$style = " class='length'";
							$value = convertS2String($row[$col]);
							break;
						case Pop:
							if ($wgArtistPop>0) 
								$pop = round($row[$col]/$wgArtistPop);
							else
								$pop = $row[$col];
							$style = " class='pop' title='{$row[$col]}'";
							$value = "<div style='position:relative;'>";
							$steps = intval(log($pop))+1; $s = 1;
							if ($steps>7) $offset = 0; else $offset = ($steps%2)*4;
							for ($i = 0; $i<$steps; $i++)
							{
								$j = $i*2+1;
								$left = 40 + ($j%16)*2*$s-intval($j/16)%2*4 - $offset;
								$top = intval($i/8)*2 -8 - intval($steps/8);
								$s = -$s;	
					                       $value .= "<img style='position:absolute; top:{$top}px; left:{$left}px;' src='/skins/soundshare/icon_user.gif'>";
							}						
							$value .="</div>";
							break;
						case More:
							$value = "<<<MORE>>>";
							$style = " style='text-align:center'";
							break;

						case Filename:
							$value = $row[$col];
							if ($value=='') $value=null; 
							
							$multirow = true;
							$style= " class='filename' colspan=".(count($columns)-1);
							break;	
						default:
							$value = $row[$col];
							break;
					}

					if ($value==null)
						continue;	
					elseif ($multirow)
						$song .= "</tr><tr><td$style>$value</td>";
					else
						$song .= "<td$style>$value</td>";
				}

				if ($group_column)
				{	
					$total_pop += $row[Pop];
					$char = 99;
					$group_name = sprintf("%04d.%02d",$gname, $char--);
					$prev = $rows[$group_name];
					if ($prev==null) {
						$prev = "<tr>$song</tr>";
						$pops[$group_name] = $row[Pop];
					} else {
						$mainPop = $pops[$group_name];
						$doubtsCount = $doubts[$group_name];
						
						if ($group_column == Track)
							$doubt = true;
						else
							$doubt = ($doubtsCount==null || $doubtsCount<5) && $mainPop>1.5*$row[Pop];	
						if ($doubt) {
							if ($doubtsCount==null) {
								$prev = mb_ereg_replace('<<<MORE>>>', "<span class='link' onclick='toggleTablePart(this, \"$group_name\");'> + </span>", $prev)."<tr class='doubt'>$song</tr>";
								$doubts[$group_name] = 1;
							} else if ($doubtsCount<7) {
								$doubts[$group_name]++;
								$prev.="<tr class='doubt'>$song</tr>";
							}
						} else {
							while ($rows[$group_name]!=null)
								$group_name = sprintf("%04d.%02d", $gname, $char--);
							$pops[$group_name] = $row[Pop];
							$prev="<tr>$song</tr>";
						}
					}
					$rows[$group_name] = $prev;
				}
				else
				{
					$rows[$page_size+1-$count] = "<tr>$song</tr>";
				}

				$count++;
                       	} while ($count<$page_size && $row = $dbr->fetchRow($res));
		
			krsort($rows);
			$alt = false;
			foreach ($rows as $group => $song)
			{
				// Hide unknown group with low pop
				$doubtsCount = $doubts[$group]+1;
				if ($doubtsCount==null) $doubtsCount=1;
				if (($group<1 && $pops[$group]<$total_pop*0.01) || $pops[$group]*$doubtsCount<$total_pop*0.01) continue;

				$song = mb_ereg_replace('<<<MORE>>>', "&nbsp;", $song);
				if ($alt)
					$style = " class='alt collapsed'";
				else
					$style = " class='collapsed'";

				$wgOut->addHTML("<tbody id='$group'$style>$song</tbody>");
				$alt = !$alt;
			}
			
			$wgOut->addHTML("</table>");
		}
               	$dbr->freeResult($res);
		if ($this->page>1 || ($count==$page_size && $count_query!=""))
		{
               		$rescount = $dbr->query($count_query);
               		if ($total_row = $dbr->fetchrow($rescount)) $found = $total_row[0];
               		$dbr->freeResult($rescount);
			if ($found>0)
			{
				$url = "?page=";
                       		$this->paging($url, $this->page, $page_size, $found);
               		}
		}
       }

       function paging($url, $page, $page_size, $found) {
               global $wgOut;

               $display_pages = 10;
               $max_page = ceil($found / $page_size);
               $center_page = $page;
               $first_page = $center_page - $display_pages / 2;
               if ($first_page < 1) $first_page = 1;
               $last_page = $first_page + $display_pages - 1;
               if ($last_page > $max_page) $last_page = $max_page;

               if ($first_page != $last_page) {
                       $wgOut->addHTML("<div class='paging'>");

                       if ($first_page > 1) {
                               $prev_page  = "&nbsp;<a href='{$url}";
                               $prev_page .= $page-1 . "'>" . wfMsg('soundsharePreviousPage') ."</a>&nbsp;";
                               $wgOut->addHTML($prev_page);
                       }
                       for ($i=$first_page; $i<$page; $i++) {
                               $wgOut->addHTML("&nbsp;<a href='{$url}{$i}'>{$i}</a>&nbsp;");
                       }
                       $wgOut->addHTML("&nbsp;<span class='cp'>{$page}</span>&nbsp;");
                       for ($i=$page+1; $i<=$last_page; $i++) {
                               $wgOut->addHTML("&nbsp;<a href='{$url}{$i}'>{$i}</a>&nbsp;");
                       }
                       if ($last_page < $max_page) {
                               $next_page  = "&nbsp;<a href='{$url}";
                               $next_page .= $page+1 . "'>" . wfMsg('soundshareNextPage') ."</a>&nbsp;";
                               $wgOut->addHTML($next_page);
                       }
                       $wgOut->addHTML("</div>");
               }
       }
}
